@page "/FileUpload"
@using System.ComponentModel.DataAnnotations
@using System.IO
@using System.Linq
@using System.Threading
@implements IDisposable

<h3>File Upload Component</h3>

<EditForm EditContext="editContext" OnValidSubmit="OnSubmit">
    <DataAnnotationsValidator />

    <div class="form-group">
        Name: <InputText @bind-Value="@person.Name" class="form-control" />
        <ValidationMessage For="() => person.Name" />
    </div>

    <div class="form-group">
        Picture: <InputFile OnChange="OnChange" class="form-control" />
        <ValidationMessage For="() => person.Picture" />

        @{
            var progressCss = "progress " + (displayProgress ? "" : "d-none");
            var progressWidthStyle = progressPercent + "%";
        }

        <div class="@progressCss">
            <div class="progress-bar" role="progressbar" style="width:@progressWidthStyle" area-valuenow="@progressPercent" aria-minvalue="0" aria-maxvalue="100"></div>
        </div>
    </div>

    <button>Submit</button>
</EditForm>

@code
{
    private CancellationTokenSource cancelation;
    private bool displayProgress;
    private EditContext editContext;
    private Person person;
    private int progressPercent;

    protected override void OnInitialized()
    {
        cancelation = new CancellationTokenSource();
        person = new Person();
        editContext = new EditContext(person);
    }

    private void OnChange(InputFileChangeEventArgs eventArgs)
    {
        person.Picture = eventArgs.File;
        editContext.NotifyFieldChanged(FieldIdentifier.Create(() => person.Picture));
    }

    private async Task OnSubmit()
    {
        using var file = File.OpenWrite(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
        using var stream = person.Picture.OpenReadStream();

        var buffer = new byte[4 * 1096];
        int bytesRead;
        double totalRead = 0;

        displayProgress = true;

        while ((bytesRead = await stream.ReadAsync(buffer, cancelation.Token)) != 0)
        {
            totalRead += bytesRead;
            await file.WriteAsync(buffer, cancelation.Token);

            progressPercent = (int)((totalRead / person.Picture.Size) * 100);
            StateHasChanged();
        }

        displayProgress = false;
    }

    public void Dispose()
    {
        cancelation.Cancel();
    }

    public class Person
    {
        [Required]
        [StringLength(20, MinimumLength = 2)]
        public string Name { get; set; } = "Pranav";

        [Required]
        [FileValidation(new[] { ".png", ".jpg" })]
        public IBrowserFile Picture { get; set; }
    }

    private class FileValidationAttribute : ValidationAttribute
    {
        public FileValidationAttribute(string[] allowedExtensions)
        {
            AllowedExtensions = allowedExtensions;
        }

        private string[] AllowedExtensions { get; }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            var file = (IBrowserFile)value;

            var extension = System.IO.Path.GetExtension(file.Name);

            if (!AllowedExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase))
            {
                return new ValidationResult($"File must have one of the following extensions: {string.Join(", ", AllowedExtensions)}.", new[] { validationContext.MemberName });
            }

            return ValidationResult.Success;
        }
    }
}
